﻿using System;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Content;
using XnaMouse = Microsoft.Xna.Framework.Input.Mouse;
using MouseState = Microsoft.Xna.Framework.Input.MouseState;
using XnaKeyboard = Microsoft.Xna.Framework.Input.Keyboard;
using KeyboardState = Microsoft.Xna.Framework.Input.KeyboardState;
using Keys = Microsoft.Xna.Framework.Input.Keys;
using ButtonState = Microsoft.Xna.Framework.Input.ButtonState;
using Microsoft.Xna.Framework.Graphics;
#if WINDOWS_PHONE
using Microsoft.Xna.Framework.Input.Touch;
#endif

namespace JGLib.States
{
    /// <summary> The main class for the state system. All custom states inherit from this class. </summary>
    [System.Diagnostics.DebuggerNonUserCode]
    public abstract class State
    {
        public static Dictionary<object, State> States;
        private static State CurrentState;

        private static KeyboardState prevKeyboard;
        private static MouseState prevMouse;

        private static ContentManager ContentManager;

        /// <summary> Gets or sets the unique ID of this state, used for switching between states. </summary>
        public object ID { get; set; }
        private Dictionary<object, object> Content;

        static State()
        {
            State.States = new Dictionary<object, State>();
            State.CurrentState = null;

            prevKeyboard = XnaKeyboard.GetState();
            prevMouse = XnaMouse.GetState();
        }
        public static void Initialize(ContentManager contentManager)
        {
            State.ContentManager = contentManager;
        }
        /// <summary>Initializes this state.</summary>
        /// <param name="ID">The unique ID of this state</param>
        public State(object ID)
        {
            this.ID = ID;
            this.Content = new Dictionary<object, object>();
        }

        /// <summary>
        /// Registers the specified state with the state system. Every state that is used must be registered.
        /// The state system begins with the first state that is registered.
        /// </summary>
        /// <param name="state">The state to be registered with the state system.</param>
        public static void RegisterState(State state)
        {
            State.States.Add(state.ID, state);
            if (State.States.Count == 1)
                State.CurrentState = state;
        }

        /// <summary>Updates the state system. Should be called from the application's main Update method.</summary>
        /// <param name="gameTime">The time, in seconds, since the last frame.</param>
        public static void UpdateState(double gameTime)
        {
            if (CurrentState != null)
                CurrentState.Update(gameTime);
        }
        /// <summary>Draws the current state. Should be called from the application's main Draw method.</summary>
        /// <param name="g">The SpriteBatch object with which to draw to the screen.</param>
        public static void DrawState(SpriteBatch g)
        {
            if (CurrentState != null)
                CurrentState.Draw(g);
        }
        /// <summary>Loads all content in the state system. Should be called from the application's main LoadContent method.</summary>
        public static void LoadStateContent()
        {
            foreach (State state in State.States.Values)
                state.LoadContent();
        }
        /// <summary>Returns the current running state, or null if the current state is null.</summary>
        /// <returns>Either current state or null.</returns>
        public static State GetCurrentState()
        {
            return State.CurrentState;
        }
        /// <summary>Calls the Initialize method of all states registered with the state system. Should be called from the application's main Initialize method.</summary>
        public static void InitializeStates()
        {
            foreach (State state in State.States.Values)
                state.Initialize();
        }

        /// <summary>Updates this state. Should be called at the end of the child state's Update method.</summary>
        /// <param name="gameTime">The time, in seconds, since the last frame.</param>
        protected virtual void Update(double gameTime)
        {
            prevKeyboard = XnaKeyboard.GetState();
            prevMouse = XnaMouse.GetState();
        }
        /// <summary>Draws this state to the screen. Does not need to be called from the child state's Draw method.</summary>
        /// <param name="g">The SpriteBatch with which to draw.</param>
        protected virtual void Draw(SpriteBatch g) { }

        /// <summary>Initializes any properties that use content properties. Assume that the call to LoadContent() has been completed. Does not need to be called from the child state's Initialize method.</summary>
        protected virtual void Initialize() { }

        /// <summary>Called when this state becomes the current state of the state system. Does not need to be called from the child state's GetSwitchedTo method.</summary>
        /// <param name="prevStateID">The ID of the previous state.</param>
        /// <param name="data">The data sent from the previous state.</param>
        protected virtual void GetSwitchedTo(object prevStateID, object[] data) { }

        /// <summary>Switches control to the state specified by its ID.</summary>
        /// <param name="id">The unique ID of the state to switch to.</param>
        protected void SwitchToState(object id)
        {
            SwitchToState(id, null);
        }
        /// <summary>Switches control to the state specified by its ID, and sends data to it.</summary>
        /// <param name="id">The unique ID of the state to switch to.</param>
        /// <param name="data">The data to send to the next state.</param>
        protected void SwitchToState(object id, params object[] data)
        {
            if (State.States.ContainsKey(id))
            {
                CurrentState = State.States[id];
                CurrentState.GetSwitchedTo(this.ID, data);
            }
            else throw new ArgumentException("No state registered with an ID of " + id
                + ". (JGLib.States.State.SwitchTo(int)).", "id");
        }

        /// <summary>Loads all of this state's content. Does not need to be called from the child's LoadContent() method.</summary>
        protected virtual void LoadContent() { }

        /// <summary>Loads a texture from the content pipeline that can later be retrieved with the GetTexture method. This method is exactly the same as LoadContent&lt;Texture2D&gt;(...).</summary>
        /// <param name="textureName">The asset name of the texture to load.</param>
        /// <param name="texID">The ID with which this texture will later be retrieved.</param>
        protected void LoadTexture(string textureName, object texID)
        {
            this.Content.Add(texID, State.ContentManager.Load<Texture2D>(textureName));
        }
        /// <summary>Retrieves a texture that has been loaded. This method is exactly the same as GetContent&lt;Texture2D&gt;(...).</summary>
        /// <param name="texID">The ID of the texture to retrieve.</param>
        /// <returns>The texture that has been associated with the ID.</returns>
        protected Texture2D GetTexture(object texID)
        {
            return (Texture2D)this.Content[texID];
        }
        /// <summary>Loads content from the content pipeline that can later be retrieved with the GetContent method.</summary>
        /// <param name="contentName">The asset name of the content to load.</param>
        /// <param name="contentID">The ID with which this content will later be retrieved.</param>
        protected void LoadContent<T>(string contentName, object contentID)
        {
            this.Content.Add(contentID, State.ContentManager.Load<T>(contentName));
        }
        /// <summary>Retrieves content that has been loaded.</summary>
        /// <param name="contentID">The ID of the content to retrieve..</param>
        /// <returns>The content that has been associated with the ID.</returns>
        protected T GetContent<T>(object contentID)
        {
            return (T)this.Content[contentID];
        }

        protected static class Key
        {
            public static bool IsDown(Keys key)
            {
                return XnaKeyboard.GetState().IsKeyDown(key);
            }
            public static bool IsUp(Keys key)
            {
                return !IsDown(key);
            }
            public static bool WasPressed(Keys key)
            {
                return IsDown(key) && State.prevKeyboard.IsKeyUp(key);
            }
            public static bool WasReleased(Keys key)
            {
                return IsUp(key) && State.prevKeyboard.IsKeyDown(key);
            }
        }
        public enum MouseButton { Left, Middle, Right }
        protected static class Mouse
        {
            public static bool IsDown(MouseButton button)
            {
                switch (button)
                {
                    case MouseButton.Left: return XnaMouse.GetState().LeftButton == ButtonState.Pressed;
                    case MouseButton.Middle: return XnaMouse.GetState().MiddleButton == ButtonState.Pressed;
                    case MouseButton.Right: return XnaMouse.GetState().RightButton == ButtonState.Pressed;

                    default: throw new Exception("You reached a logically impossible point in the program. (JGLib.States.State.Mouse.IsDown(JGLib.States.State.MouseButton).");
                }
            }
            public static bool IsUp(MouseButton button)
            {
                return !IsDown(button);
            }
            public static bool WasPressed(MouseButton button)
            {
                if (!IsDown(button))
                    return false;
                switch (button)
                {
                    case MouseButton.Left: return State.prevMouse.LeftButton == ButtonState.Released;
                    case MouseButton.Middle: return State.prevMouse.MiddleButton == ButtonState.Released;
                    case MouseButton.Right: return State.prevMouse.RightButton == ButtonState.Released;

                    default: throw new Exception("You reached a logically impossible point in the program. (JGLib.States.State.Mouse.WasPressed(JGLib.States.State.MouseButton).");
                }
            }
            public static bool WasReleased(MouseButton button)
            {
                if (IsDown(button))
                    return false;
                switch (button)
                {
                    case MouseButton.Left: return State.prevMouse.LeftButton == ButtonState.Pressed;
                    case MouseButton.Middle: return State.prevMouse.MiddleButton == ButtonState.Pressed;
                    case MouseButton.Right: return State.prevMouse.RightButton == ButtonState.Pressed;

                    default: throw new Exception("You reached a logically impossible point in the program. (JGLib.States.State.Mouse.WasReleased(JGLib.States.State.MouseButton).");
                }
            }
            public static bool WasMoved()
            {
                return XnaMouse.GetState().X != prevMouse.X || XnaMouse.GetState().Y != prevMouse.Y;
            }
            public static Vector2 Movement
            {
                get { return new Vector2(XnaMouse.GetState().X - prevMouse.X, XnaMouse.GetState().Y - prevMouse.Y); }
            }
            public static bool WasDragged(MouseButton button)
            {
                if (IsUp(button) || !WasMoved())
                    return false;
                switch (button)
                {
                    case MouseButton.Left: return prevMouse.LeftButton == ButtonState.Pressed;
                    case MouseButton.Middle: return prevMouse.MiddleButton == ButtonState.Pressed;
                    case MouseButton.Right: return prevMouse.RightButton == ButtonState.Pressed;
                    default: throw new Exception("You reached a logically impossible point in the program. (JGLib.States.State.Mouse.WasDragged(JGLib.States.State.MouseButton).");
                }
            }
            public static bool WasScrolled()
            {
                return XnaMouse.GetState().ScrollWheelValue != prevMouse.ScrollWheelValue;
            }
            public static int Scroll
            {
                get { return XnaMouse.GetState().ScrollWheelValue - prevMouse.ScrollWheelValue; }
            }
            public static Vector2 Location
            {
                get { return new Vector2(XnaMouse.GetState().X, XnaMouse.GetState().Y); }
            }
        }
#if WINDOWS_PHONE
        protected static class Touch
        {
            public static TouchCollection Touches { get { return TouchPanel.GetState(); } }
            public static List<GestureSample> Gestures
            {
                get
                {
                    List<GestureSample> gestures = new List<GestureSample>();
                    while (TouchPanel.IsGestureAvailable)
                        gestures.Add(TouchPanel.ReadGesture());
                    return gestures;
                }
            }
        }
#endif
    }
}
